package at.grabner.circleprogress;

import at.grabner.circleprogress.curvetypes.TimeCurveType;
import ohos.agp.colors.RgbPalette;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.*;
import ohos.agp.text.Font;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.Rect;
import ohos.agp.utils.RectFloat;
import ohos.ai.cv.common.CvPoint;
import ohos.app.Context;
import ohos.eventhandler.InnerEvent;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.MmiPoint;
import ohos.multimodalinput.event.TouchEvent;

import java.text.DecimalFormat;

public class CircleProgressView extends Component implements Component.DrawTask, Component.TouchEventListener, Component.LayoutRefreshedListener {
    /**
     * The log tag.
     */
    private static final boolean DEBUG = false;
    //----------------------------------
    //region members
    //Colors (with defaults)

    private final int mBarColorStandard = RgbPalette.parse("#0099ff"); //stylish blue
    protected int mLayoutHeight = 0;
    protected int mLayoutWidth = 0;
    //Rectangles
    protected RectFloat mCircleBounds = new RectFloat();
    protected RectFloat mInnerCircleBound = new RectFloat();
    protected CvPoint mCenter;
    /**
     * Maximum size of the text.
     */
    protected RectFloat mOuterTextBounds = new RectFloat();
    /**
     * Actual size of the text.
     */
    protected RectFloat mActualTextBounds = new RectFloat();
    protected RectFloat mUnitBounds = new RectFloat();
    protected RectFloat mCircleOuterContour = new RectFloat();
    protected RectFloat mCircleInnerContour = new RectFloat();
    //value animation
    Direction mDirection = Direction.CW;
    float mCurrentValue = 0;
    float mValueTo = 0;
    float mValueFrom = 0;
    float mMaxValue = 100;
    float mMinValueAllowed = 0;
    float mMaxValueAllowed = -1;
    // spinner animation
    float mSpinningBarLengthCurrent = 0;
    float mSpinningBarLengthOrig = 42;
    float mCurrentSpinnerDegreeValue = 0;
    //Animation
    //The amount of degree to move the bar by on each draw
    float mSpinSpeed = 2.8f;
    //Enable spin
    boolean mSpin = false;
    /**
     * The animation duration in ms
     */
    double mAnimationDuration = 900;
    //The number of milliseconds to wait in between each draw
    int mFrameDelayMillis = 10;
    // helper for AnimationState.END_SPINNING_START_ANIMATING
    boolean mDrawBarWhileSpinning;
    //The animation handler containing the animation state machine.
    AnimationHandler mAnimationHandler = new AnimationHandler(this);
    //The current state of the animation state machine.
    AnimationState mAnimationState = AnimationState.IDLE;
    AnimationStateChangedListener mAnimationStateChangedListener;
    private int mBarWidth = 100;
    private int mRimWidth = 100;
    private int mStartAngle = 270;
    private float mOuterContourSize = 1;
    private float mInnerContourSize = 1;

    // Bar start/end width and type
    private int mBarStartEndLineWidth = 0;
    private BarStartEndLine mBarStartEndLine = BarStartEndLine.NONE;
     private int mBarStartEndLineColor = RgbPalette.parse("#000000");
    private float mBarStartEndLineSweep = 10f;
    //Default text sizes
    private int mUnitTextSize = 60;
    private int mTextSize = 60;
    //Text scale
    private float mTextScale = 1;
    private float mUnitScale = 1;
    private int mOuterContourColor = RgbPalette.parse("#FFFFFF");
    private int mInnerContourColor = RgbPalette.parse("#99AABB");
    private int mSpinnerColor = mBarColorStandard; //stylish blue
    private int mRimColor = RgbPalette.parse("#80ccff");
    private int mTextColor = RgbPalette.parse("#0099ff");
    private int mUnitColor = RgbPalette.parse("#FF0000");
    private int mBackgroundCircleColor = 0x00000000;  //transparent
    private boolean mIsAutoColorEnabled = false;
    private int[] mBarColors = new int[]{
            mBarColorStandard //stylish blue
    };

    private Color[] mBarColor = new Color[]{
            Color.BLUE //stylish blue
    };
    //Caps
    private Paint.StrokeCap mBarStrokeCap = Paint.StrokeCap.BUTT_CAP;
    private Paint.StrokeCap mSpinnerStrokeCap = Paint.StrokeCap.BUTT_CAP;
    //Paints
    private Paint mBarPaint = new Paint();
    private Paint mShaderlessBarPaint;
    private Paint mBarSpinnerPaint = new Paint();
    private Paint mBarStartEndLinePaint = new Paint();
    private Paint mBackgroundCirclePaint = new Paint();
    private Paint mRimPaint = new Paint();
    private Paint mTextPaint = new Paint();
    private Paint mUnitTextPaint = new Paint();
    private Paint mOuterContourPaint = new Paint();
    private Paint mInnerContourPaint = new Paint();
    //Other
    // The text to show
    private String mText = "";
    private int mTextLength;
    private String mUnit = "";
    private UnitPosition mUnitPosition = UnitPosition.RIGHT_TOP;
    /**
     * Indicates if the given text, the current percentage, or the current value should be shown.
     */
    private TextMode mTextMode = TextMode.PERCENT;
    private boolean mIsAutoTextSize;
    private boolean mShowUnit = false;
    //clipping
    private PixelMap mClippingBitmap;
    private Paint mMaskPaint;
    /**
     * Relative size of the unite string to the value string.
     */
    private float mRelativeUniteSize = 1f;
    private boolean mSeekModeEnabled = false;
    private boolean mShowTextWhileSpinning = false;
    private boolean mShowBlock = false;
    private int mBlockCount = 18;
    private float mBlockScale = 0.9f;
    private float mBlockDegree = 360 / mBlockCount;
    private float mBlockScaleDegree = mBlockDegree * mBlockScale;
    private boolean mRoundToBlock = false;
    private boolean mRoundToWholeNumber = false;

    private int mTouchEventCount;
    private OnProgressChangedListener onProgressChangedListener;
    private float previousProgressChangedValue;

    private Arc arc = new Arc();

    private DecimalFormat decimalFormat = new DecimalFormat("0");

      /* Text */
    private static final String CircleProgressView_cpv_text = "cpv_text";
    private static final String CircleProgressView_cpv_unit = "cpv_unit";
    private static final String CircleProgressView_cpv_textColor = "cpv_textColor";
    private static final String CircleProgressView_cpv_unitColor = "cpv_unitColor";
    private static final String CircleProgressView_cpv_showUnit = "cpv_showUnit";
    private static final String CircleProgressView_cpv_autoTextColor = "cpv_autoTextColor";


    private static final String CircleProgressView_cpv_spinSpeed = "cpv_spinSpeed";
    private static final String CircleProgressView_cpv_spin = "cpv_spin";
    private static final String CircleProgressView_cpv_direction = "cpv_direction";
    private static final String CircleProgressView_cpv_value = "cpv_value";
    private static final String CircleProgressView_cpv_barColor = "cpv_barColor";
    private static final String CircleProgressView_cpv_barColor1 = "cpv_barColor1";
    private static final String CircleProgressView_cpv_barColor2 = "cpv_barColor2";
    private static final String CircleProgressView_cpv_barColor3 = "cpv_barColor3";
    private static final String CircleProgressView_cpv_barStrokeCap = "cpv_barStrokeCap";
    private static final String CircleProgressView_cpv_barStartEndLineWidth = "cpv_barStartEndLineWidth";
    private static final String CircleProgressView_cpv_barStartEndLine = "cpv_barStartEndLine";
    private static final String CircleProgressView_cpv_spinBarLength = "cpv_spinBarLength";
    private static final String CircleProgressView_cpv_textSize = "cpv_textSize";
    private static final String CircleProgressView_cpv_unitSize = "cpv_unitSize";

    private static final String CircleProgressView_cpv_autoTextSize = "cpv_autoTextSize";
    private static final String CircleProgressView_cpv_textMode = "cpv_textMode";
    private static final String CircleProgressView_cpv_unitPosition = "cpv_unitPosition";

    private static final String CircleProgressView_cpv_unitToTextScale = "cpv_unitToTextScale";
    private static final String CircleProgressView_cpv_rimColor = "cpv_rimColor";
    private static final String CircleProgressView_cpv_maxValue = "cpv_maxValue";
    private static final String CircleProgressView_cpv_minValueAllowed = "cpv_minValueAllowed";
    private static final String CircleProgressView_cpv_maxValueAllowed = "cpv_maxValueAllowed";
    private static final String CircleProgressView_cpv_roundToBlock = "cpv_roundToBlock";
    private static final String CircleProgressView_cpv_roundToWholeNumber = "cpv_roundToWholeNumber";


    private static final String CircleProgressView_cpv_textScale = "cpv_textScale";
    private static final String CircleProgressView_cpv_unitScale = "cpv_unitScale";
    private static final String CircleProgressView_cpv_seekMode = "cpv_seekMode";
    private static final String CircleProgressView_cpv_startAngle = "cpv_startAngle";
    private static final String CircleProgressView_cpv_showTextInSpinningMode = "cpv_showTextInSpinningMode";
    private static final String CircleProgressView_cpv_blockCount = "cpv_blockCount";
    private static final String CircleProgressView_cpv_blockScale = "cpv_blockScale";
    private static final String CircleProgressView_cpv_barStartEndLineColor = "cpv_barStartEndLineColor";
    private static final String CircleProgressView_cpv_barStartEndLineSweep = "cpv_barStartEndLineSweep";

    private static final String CircleProgressView_cpv_innerContourSize = "cpv_innerContourSize";
    private static final String CircleProgressView_cpv_innerContourColor = "cpv_innerContourColor";

    private static final String CircleProgressView_cpv_outerContourSize = "cpv_outerContourSize";
    private static final String CircleProgressView_cpv_outerContourColor = "cpv_outerContourColor";

    private static final String CircleProgressView_cpv_fillColor = "cpv_fillColor";
    private static final String CircleProgressView_cpv_rimWidth = "cpv_rimWidth";
    private static final String CircleProgressView_cpv_barWidth = "cpv_barWidth";
    private static final String CircleProgressView_cpv_spinColor = "cpv_spinColor";

    // Text typeface
    private Font textTypeface;
    private Font unitTextTypeface;

    public CircleProgressView(Context context) {
        super(context);
    }

    public CircleProgressView(Context context, AttrSet attrSet) {
        super(context, attrSet);
        if (attrSet != null) {
            parseAttributes(attrSet);
        } else {
            mBarPaint.setStrokeWidth(mBarWidth);
            mBarSpinnerPaint.setStrokeWidth(mBarWidth);
            setBarWidth(mBarWidth);
            mRimPaint.setStrokeWidth(mRimWidth);
            setRimWidth(mRimWidth);
            setSpinSpeed(mSpinSpeed);
            setSpin(mSpin);
            setDirection(Direction.values()[0]);
            setValue(mCurrentValue);
            setSpinnerStrokeCap(Paint.StrokeCap.SQUARE_CAP);
            setFillCircleColor(mBackgroundCircleColor);
            setBarStartEndLine(0,
                    BarStartEndLine.values()[3],
                    mBarStartEndLineColor, mBarStartEndLineSweep);
            mBarSpinnerPaint.setColor(new Color(mSpinnerColor));
            setSpinningBarLength( mSpinningBarLengthOrig);
            this.mTextPaint.setTextSize(mTextSize);
            mIsAutoTextSize = false;
            this.mUnitTextPaint.setTextSize(mUnitTextSize);
            mTextPaint.setColor(new Color(mTextColor));
            mUnitTextPaint.setColor(new Color(mBarColorStandard));
            mIsAutoColorEnabled = false;
            mIsAutoColorEnabled = false;
            mIsAutoTextSize = true;
            setUnitPosition(UnitPosition.values()[3]);
            setUnitToTextScale( 1f);
            setRimColor( mRimColor);
            mRimPaint.setColor(new Color(mRimColor));
            mOuterContourPaint.setColor(new Color(mOuterContourColor));
            mOuterContourPaint.setStrokeWidth(mOuterContourSize);
            mInnerContourPaint.setColor(new Color(mInnerContourColor));
            setInnerContourColor( mInnerContourColor);
            setInnerContourSize( mInnerContourSize);
            mInnerContourPaint.setStrokeWidth(mInnerContourSize);
            setOuterContourColor( mOuterContourColor);
            setOuterContourSize( mOuterContourSize);
            setMaxValue( mMaxValue);
            setMinValueAllowed( mMinValueAllowed);
            setMaxValueAllowed(mMaxValueAllowed);
            setRoundToBlock( mRoundToBlock);
            setRoundToWholeNumber(mRoundToWholeNumber);
            setUnit( "%");
            setUnitVisible( mShowUnit);
            setTextScale(mTextScale);
            setUnitScale( mUnitScale);
            setSeekModeEnabled( mSeekModeEnabled);

            setStartAngle( mStartAngle);

            setShowTextWhileSpinning(mShowTextWhileSpinning);


            setBlockCount(1);
        }

        mMaskPaint = new Paint();
        mMaskPaint.setAntiAlias(true);
        mMaskPaint.setFilterBitmap(false);

        Canvas canvas = new Canvas();
        canvas.drawPaint(mMaskPaint);
        Canvas.PorterDuffMode.valueOf(String.valueOf(Canvas.PorterDuffMode.DST_IN));
        setupPaints();

        setLayoutRefreshedListener(this::onRefreshed);
        onSizeChanged();
        addDrawTask(this::onDraw);
        setTouchEventListener(this::onTouchEvent);
        if (mSpin) {
            spin();
        }
    }

    public CircleProgressView(Context context, AttrSet attrSet, String styleName) {
        super(context, attrSet, styleName);
    }

    public CircleProgressView(Context context, AttrSet attrSet, int resId) {
        super(context, attrSet, resId);
    }

    private static float calcTextSizeForRect(String _text, Paint _textPaint, RectFloat _rectBounds) {

        Matrix matrix = new Matrix();
        Rect textBoundsTmp = new Rect();
        //replace ones because for some fonts the 1 takes less space which causes issues
        String text = _text.replace('1', '0');

        //get current mText bounds
        textBoundsTmp = _textPaint.getTextBounds(text);

        RectFloat textBoundsTmpF = new RectFloat(textBoundsTmp);

        matrix.setRectToRect(textBoundsTmpF, _rectBounds, Matrix.ScaleToFit.START);
        float values[] = new float[9];
        matrix.getElements(values);
        return _textPaint.getTextSize() * values[0];
    }

    /**
     * Calculates the angle from centerPt to targetPt in degrees.
     * The return should range from [0,360), rotating CLOCKWISE,
     * 0 and 360 degrees represents EAST,
     * 90 degrees represents SOUTH, etc...
     * <p/>
     * Assumes all points are in the same coordinate space.  If they are not,
     * you will need to call SwingUtilities.convertPointToScreen or equivalent
     * on all arguments before passing them  to this function.
     *
     * @param centerPt Point we are rotating around.
     * @param targetPt Point we want to calculate the angle to.
     * @return angle in degrees.  This is the angle from centerPt to targetPt.
     */
    public static double calcRotationAngleInDegrees(CvPoint centerPt, CvPoint targetPt) {
        // calculate the angle theta from the deltaY and deltaX values
        // (atan2 returns radians values from [-PI,PI])
        // 0 currently points EAST.
        // NOTE: By preserving Y and X param order to atan2,  we are expecting
        // a CLOCKWISE angle direction.
        double theta = Math.atan2(targetPt.y - centerPt.y, targetPt.x - centerPt.x);

        // rotate the theta angle clockwise by 90 degrees
        // (this makes 0 point NORTH)
        // NOTE: adding to an angle rotates it clockwise.
        // subtracting would rotate it counter-clockwise
//        theta += Math.PI/2.0;

        // convert from radians to degrees
        // this will give you an angle from [0->270],[-180,0]
        double angle = Math.toDegrees(theta);

        // convert to positive range [0-360)
        // since we want to prevent negative angles, adjust them now.
        // we can assume that atan2 will not return a negative value
        // greater than one partial rotation
        if (angle < 0) {
            angle += 360;
        }

        return angle;
    }

    //----------------------------------
    //region getter/setter
    public BarStartEndLine getBarStartEndLine() {
        return mBarStartEndLine;
    }

    public int[] getBarColors() {
        return mBarColors;
    }

    public Paint.StrokeCap getBarStrokeCap() {
        return mBarStrokeCap;
    }


    /**
     * Parse the attributes passed to the view from the XML
     *
     * @param attrs the attributes to parse
     */
    private void parseAttributes(AttrSet attrs) {

        mBarPaint.setStrokeWidth(mBarWidth);
        mBarSpinnerPaint.setStrokeWidth(mBarWidth);

        mRimPaint.setStrokeWidth(mRimWidth);

        setBarWidth(attrs.getAttr(CircleProgressView_cpv_barWidth).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_barWidth).get().getIntegerValue() :
                mBarWidth);

       setSpinBarColor(attrs.getAttr(CircleProgressView_cpv_spinColor).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_spinColor).get().getColorValue().getValue(): mSpinnerColor);

        setRimWidth(attrs.getAttr(CircleProgressView_cpv_rimWidth).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_rimWidth).get().getIntegerValue() :
                mRimWidth);

        setSpinSpeed(attrs.getAttr(CircleProgressView_cpv_spinSpeed).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_spinSpeed).get().getFloatValue() : mSpinSpeed);


        setSpin(attrs.getAttr(CircleProgressView_cpv_spin).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_spin).get().getBoolValue() : mSpin);

        if (attrs.getAttr(CircleProgressView_cpv_direction).isPresent()) {
            if (attrs.getAttr(CircleProgressView_cpv_direction).get().getIntegerValue() == 0 || attrs.getAttr(CircleProgressView_cpv_direction).get().getIntegerValue() == 1) {
                setDirection(Direction.values()[attrs.getAttr(CircleProgressView_cpv_direction).isPresent() ? attrs.getAttr(
                        CircleProgressView_cpv_direction).get().getIntegerValue() : 0]);
            } else {
                setDirection(Direction.values()[0]);
            }
        }

        float value = attrs.getAttr(CircleProgressView_cpv_value).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_value).get().getFloatValue() : mCurrentValue;

        setValue(value);
        mCurrentValue = value;

        if (attrs.getAttr(CircleProgressView_cpv_barStrokeCap).isPresent()) {
            if (attrs.getAttr(CircleProgressView_cpv_barStrokeCap).get().getIntegerValue() == 0) {
                setSpinnerStrokeCap(Paint.StrokeCap.BUTT_CAP);
            } else if (attrs.getAttr(CircleProgressView_cpv_barStrokeCap).get().getIntegerValue() == 1) {
                setSpinnerStrokeCap(Paint.StrokeCap.ROUND_CAP);
            } else {
                setSpinnerStrokeCap(Paint.StrokeCap.SQUARE_CAP);
            }
        }

        if (attrs.getAttr(CircleProgressView_cpv_barColor).isPresent() && attrs.getAttr(CircleProgressView_cpv_barColor1).isPresent() &&
                attrs.getAttr(CircleProgressView_cpv_barColor2).isPresent() && attrs.getAttr(CircleProgressView_cpv_barColor3).isPresent()) {
            mBarColors = new int[]{attrs.getAttr(CircleProgressView_cpv_barColor).isPresent() ? attrs.getAttr(
                    CircleProgressView_cpv_barColor).get().getColorValue().getValue() : mBarColorStandard,
                    attrs.getAttr(CircleProgressView_cpv_barColor1).isPresent() ? attrs.getAttr(
                            CircleProgressView_cpv_barColor1).get().getColorValue().getValue() : mBarColorStandard,
                    attrs.getAttr(CircleProgressView_cpv_barColor2).isPresent() ? attrs.getAttr(
                            CircleProgressView_cpv_barColor2).get().getColorValue().getValue() : mBarColorStandard,
                    attrs.getAttr(CircleProgressView_cpv_barColor3).isPresent() ? attrs.getAttr(
                            CircleProgressView_cpv_barColor3).get().getColorValue().getValue() : mBarColorStandard};

            mBarColor = new Color[]{attrs.getAttr(CircleProgressView_cpv_barColor).isPresent() ? attrs.getAttr(
                    CircleProgressView_cpv_barColor).get().getColorValue() : Color.BLUE,
                    attrs.getAttr(CircleProgressView_cpv_barColor1).isPresent() ? attrs.getAttr(
                            CircleProgressView_cpv_barColor1).get().getColorValue() : Color.RED,
                    attrs.getAttr(CircleProgressView_cpv_barColor2).isPresent() ? attrs.getAttr(
                            CircleProgressView_cpv_barColor2).get().getColorValue() : Color.GREEN,
                    attrs.getAttr(CircleProgressView_cpv_barColor3).isPresent() ? attrs.getAttr(
                            CircleProgressView_cpv_barColor3).get().getColorValue() : Color.CYAN

            };

        } else if (attrs.getAttr(CircleProgressView_cpv_barColor).isPresent() && attrs.getAttr(CircleProgressView_cpv_barColor1).isPresent() &&
                attrs.getAttr(CircleProgressView_cpv_barColor2).isPresent()) {
            mBarColors = new int[]{attrs.getAttr(CircleProgressView_cpv_barColor).isPresent() ? attrs.getAttr(
                    CircleProgressView_cpv_barColor).get().getColorValue().getValue() : mBarColorStandard,
                    attrs.getAttr(CircleProgressView_cpv_barColor1).isPresent() ? attrs.getAttr(
                            CircleProgressView_cpv_barColor1).get().getColorValue().getValue() : mBarColorStandard,
                    attrs.getAttr(CircleProgressView_cpv_barColor2).isPresent() ? attrs.getAttr(
                            CircleProgressView_cpv_barColor2).get().getColorValue().getValue() : mBarColorStandard};
            mBarColor = new Color[]{attrs.getAttr(CircleProgressView_cpv_barColor).isPresent() ? attrs.getAttr(
                    CircleProgressView_cpv_barColor).get().getColorValue() : Color.BLUE,
                    attrs.getAttr(CircleProgressView_cpv_barColor1).isPresent() ? attrs.getAttr(
                            CircleProgressView_cpv_barColor1).get().getColorValue() : Color.RED,
                    attrs.getAttr(CircleProgressView_cpv_barColor2).isPresent() ? attrs.getAttr(
                            CircleProgressView_cpv_barColor2).get().getColorValue() : Color.GREEN,
            };

        } else if (attrs.getAttr(CircleProgressView_cpv_barColor).isPresent() && attrs.getAttr(CircleProgressView_cpv_barColor1).isPresent()) {
            mBarColors = new int[]{attrs.getAttr(CircleProgressView_cpv_barColor).isPresent() ? attrs.getAttr(
                    CircleProgressView_cpv_barColor).get().getColorValue().getValue() : mBarColorStandard,
                    attrs.getAttr(CircleProgressView_cpv_barColor1).isPresent() ? attrs.getAttr(
                            CircleProgressView_cpv_barColor1).get().getColorValue().getValue() : mBarColorStandard};

            mBarColor = new Color[]{attrs.getAttr(CircleProgressView_cpv_barColor).isPresent() ? attrs.getAttr(
                    CircleProgressView_cpv_barColor).get().getColorValue() : Color.BLUE,
                    attrs.getAttr(CircleProgressView_cpv_barColor1).isPresent() ? attrs.getAttr(
                            CircleProgressView_cpv_barColor1).get().getColorValue() : Color.RED
            };
        } else {
            mBarColors = new int[]{attrs.getAttr(CircleProgressView_cpv_barColor).isPresent() ? attrs.getAttr(
                    CircleProgressView_cpv_barColor).get().getColorValue().getValue() : mBarColorStandard,
                    attrs.getAttr(CircleProgressView_cpv_barColor).isPresent() ? attrs.getAttr(
                            CircleProgressView_cpv_barColor).get().getColorValue().getValue() : mBarColorStandard};

        }

        if(attrs.getAttr(CircleProgressView_cpv_fillColor).isPresent()) {
            setFillCircleColor(attrs.getAttr(CircleProgressView_cpv_fillColor).get().getColorValue().getValue());
        }



        if (attrs.getAttr(CircleProgressView_cpv_barStartEndLineWidth).isPresent() && attrs.getAttr(CircleProgressView_cpv_barStartEndLine).isPresent()) {
            if (attrs.getAttr(CircleProgressView_cpv_barStartEndLine).get().getIntegerValue() >=0 &&
                    attrs.getAttr(CircleProgressView_cpv_barStartEndLine).get().getDimensionValue()<=3) {
                        setBarStartEndLine(attrs.getAttr(CircleProgressView_cpv_barStartEndLineWidth).isPresent() ? attrs.getAttr(
                        CircleProgressView_cpv_barStartEndLineWidth).get().getDimensionValue() : 0,
                        BarStartEndLine.values()[attrs.getAttr(CircleProgressView_cpv_barStartEndLineWidth).isPresent() ? attrs.getAttr(
                                CircleProgressView_cpv_barStartEndLine).get().getDimensionValue() : 3],
                        attrs.getAttr(CircleProgressView_cpv_barStartEndLineColor).isPresent() ? attrs.getAttr(
                                CircleProgressView_cpv_barStartEndLineColor).get().getColorValue().getValue() : mBarStartEndLineColor,
                        attrs.getAttr(CircleProgressView_cpv_barStartEndLineSweep).isPresent() ? attrs.getAttr(
                                CircleProgressView_cpv_barStartEndLineSweep).get().getFloatValue() : mBarStartEndLineSweep);
            } else {
                        setBarStartEndLine( 0,
                        BarStartEndLine.values()[3],
                        mBarStartEndLineColor, mBarStartEndLineSweep);
            }
        }

        mBarSpinnerPaint.setColor(new Color(mSpinnerColor));

        setSpinningBarLength(attrs.getAttr(CircleProgressView_cpv_spinBarLength).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_spinBarLength).get().getFloatValue() : mSpinningBarLengthOrig);

        if (attrs.getAttr(CircleProgressView_cpv_textSize).isPresent()) {
            mTextSize = attrs.getAttr(CircleProgressView_cpv_textSize).get().getDimensionValue();
            this.mTextPaint.setTextSize(mTextSize);
            mIsAutoTextSize = false;

        }
        if (attrs.getAttr(CircleProgressView_cpv_unitSize).isPresent()) {
            mUnitTextSize = attrs.getAttr(CircleProgressView_cpv_unitSize).get().getDimensionValue();
            this.mUnitTextPaint.setTextSize(mUnitTextSize);
        }
        if (attrs.getAttr(CircleProgressView_cpv_textColor).isPresent()) {
            mTextPaint.setColor(new Color(mTextColor));
        }
        if (attrs.getAttr(CircleProgressView_cpv_unitColor).isPresent()) {
            int color = attrs.getAttr(CircleProgressView_cpv_unitColor).get().getColorValue().getValue();
            mUnitTextPaint.setColor(new Color(color));
            mIsAutoColorEnabled = false;
        }
        if (attrs.getAttr(CircleProgressView_cpv_autoTextColor).isPresent()) {
            mIsAutoColorEnabled = false;
        }
        if (attrs.getAttr(CircleProgressView_cpv_autoTextSize).isPresent()) {
            mIsAutoTextSize = true;
        }
        if (attrs.getAttr(CircleProgressView_cpv_textMode).isPresent()) {
            if (attrs.getAttr(CircleProgressView_cpv_textMode).get().getIntegerValue() >= 0 && attrs.getAttr(
                    CircleProgressView_cpv_textMode).get().getIntegerValue() <=2) {
                setTextMode(TextMode.values()[attrs.getAttr(CircleProgressView_cpv_textMode).isPresent() ? attrs.getAttr(
                        CircleProgressView_cpv_textMode).get().getIntegerValue() : 0]);
            } else {
                setTextMode(TextMode.values()[0]);
            }
        }
        if (attrs.getAttr(CircleProgressView_cpv_unitPosition).isPresent()) {
            if (attrs.getAttr(CircleProgressView_cpv_unitPosition).get().getIntegerValue() >=0 && attrs.getAttr(CircleProgressView_cpv_unitPosition).get().getIntegerValue() <= 5 ) {
                setUnitPosition(UnitPosition.values()[attrs.getAttr(CircleProgressView_cpv_unitPosition).isPresent() ? attrs.getAttr(
                        CircleProgressView_cpv_unitPosition).get().getIntegerValue() : 2]);
            } else {
                setUnitPosition(UnitPosition.values()[3]);
            }
        }

        //if the mText is empty, show current percentage value
        if (attrs.getAttr(CircleProgressView_cpv_text).isPresent()) {
            setText(attrs.getAttr(CircleProgressView_cpv_text).get().getStringValue());
        }

        setUnitToTextScale(attrs.getAttr(CircleProgressView_cpv_unitToTextScale).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_unitToTextScale).get().getFloatValue() : 1f);

        setRimColor(attrs.getAttr(CircleProgressView_cpv_rimColor).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_rimColor).get().getColorValue().getValue() : mRimColor);

        mRimPaint.setColor(new Color(mRimColor));

        mOuterContourPaint.setColor(new Color(mOuterContourColor));

        mOuterContourPaint.setStrokeWidth(mOuterContourSize);

        mInnerContourPaint.setColor(new Color(mInnerContourColor));

        setInnerContourColor(attrs.getAttr(CircleProgressView_cpv_innerContourColor).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_innerContourColor).get().getColorValue().getValue() :  mInnerContourColor);

        setInnerContourSize(attrs.getAttr(CircleProgressView_cpv_innerContourSize).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_innerContourSize).get().getDimensionValue() : mInnerContourSize);
        mInnerContourPaint.setStrokeWidth(mInnerContourSize);

        setOuterContourColor(attrs.getAttr(CircleProgressView_cpv_outerContourColor).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_outerContourColor).get().getColorValue().getValue() :  mOuterContourColor);

        setOuterContourSize(attrs.getAttr(CircleProgressView_cpv_outerContourSize).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_outerContourSize).get().getDimensionValue() : mOuterContourSize);


        setMaxValue(attrs.getAttr(CircleProgressView_cpv_maxValue).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_maxValue).get().getFloatValue() : mMaxValue);


        setMinValueAllowed(attrs.getAttr(CircleProgressView_cpv_minValueAllowed).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_minValueAllowed).get().getFloatValue() : mMinValueAllowed);

        setMaxValueAllowed(attrs.getAttr(CircleProgressView_cpv_maxValueAllowed).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_maxValueAllowed).get().getFloatValue() : mMaxValueAllowed);

        setRoundToBlock(attrs.getAttr(CircleProgressView_cpv_roundToBlock).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_roundToBlock).get().getBoolValue() : mRoundToBlock);

        setRoundToWholeNumber(attrs.getAttr(CircleProgressView_cpv_roundToWholeNumber).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_roundToWholeNumber).get().getBoolValue() : mRoundToWholeNumber);

        setUnit(attrs.getAttr(CircleProgressView_cpv_unit).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_unit).get().getStringValue() : "%");

        setUnitVisible(attrs.getAttr(CircleProgressView_cpv_showUnit).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_showUnit).get().getBoolValue() : mShowUnit);

        setTextScale(attrs.getAttr(CircleProgressView_cpv_textScale).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_textScale).get().getFloatValue() : mTextScale);

        setUnitScale(attrs.getAttr(CircleProgressView_cpv_unitScale).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_unitScale).get().getFloatValue() : mUnitScale);

        setSeekModeEnabled(attrs.getAttr(CircleProgressView_cpv_seekMode).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_seekMode).get().getBoolValue() : mSeekModeEnabled);

        setStartAngle(attrs.getAttr(CircleProgressView_cpv_startAngle).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_startAngle).get().getIntegerValue() : mStartAngle);

        setShowTextWhileSpinning(attrs.getAttr(CircleProgressView_cpv_showTextInSpinningMode).isPresent() ? attrs.getAttr(
                CircleProgressView_cpv_showTextInSpinningMode).get().getBoolValue() : mShowTextWhileSpinning);

        if (attrs.getAttr(CircleProgressView_cpv_blockCount).isPresent()) {
            setBlockCount(attrs.getAttr(CircleProgressView_cpv_blockCount).isPresent() ? attrs.getAttr(
                    CircleProgressView_cpv_blockCount).get().getIntegerValue() : 1);
            setBlockScale(attrs.getAttr(CircleProgressView_cpv_blockScale).isPresent() ? attrs.getAttr(
                    CircleProgressView_cpv_blockScale).get().getFloatValue() : 0.9f);
        }

    }

    public float getBlockScale() {
        return mBlockScale;
    }

    public void setBlockScale(float blockScale) {
        if (blockScale >= 0.0f && blockScale <= 1.0f) {
            mBlockScale = blockScale;
            mBlockScaleDegree = mBlockDegree * blockScale;
        }
    }

    public int getBlockCount() {
        return mBlockCount;
    }

    public void setBlockCount(int blockCount) {
        if (blockCount > 1) {
            mShowBlock = true;
            mBlockCount = blockCount;
            mBlockDegree = 360.0f / blockCount;
            mBlockScaleDegree = mBlockDegree * mBlockScale;
        } else {
            mShowBlock = false;
        }
    }


    public void setShowTextWhileSpinning(boolean shouldDrawTextWhileSpinning) {
        mShowTextWhileSpinning = shouldDrawTextWhileSpinning;
    }

    public void setStartAngle(int _startAngle) {
        // get a angle between 0 and 360
        mStartAngle = (int) normalizeAngle(_startAngle);
    }


    private static float normalizeAngle(float _angle) {
        return (((_angle % 360) + 360) % 360);
    }

    public void setSeekModeEnabled(boolean _seekModeEnabled) {
        mSeekModeEnabled = _seekModeEnabled;
    }

    /**
     * Scale factor for unit text next to the main text.
     * Only used if auto text size is enabled.
     *
     * @param _unitScale The scale value.
     */
    public void setUnitScale(float _unitScale) {
        mUnitScale = _unitScale;
    }


    public void setUnitVisible(boolean _showUnit) {
        if (_showUnit != mShowUnit) {
            mShowUnit = _showUnit;
            triggerReCalcTextSizesAndPositions(); // triggers recalculating text sizes
        }
    }

    private void triggerReCalcTextSizesAndPositions() {
        mTextLength = -1;
        mOuterTextBounds = getInnerCircleRect(mCircleBounds);
        this.invalidate();
    }

    private RectFloat getInnerCircleRect(RectFloat _circleBounds) {

        double circleWidth = +_circleBounds.getWidth() - (Math.max(mBarWidth, mRimWidth)) - mOuterContourSize - mInnerContourSize;
        double width = ((circleWidth / 2d) * Math.sqrt(2d));
        float widthDelta = (_circleBounds.getWidth() - (float) width) / 2f;

        float scaleX = 1;
        float scaleY = 1;
        if (isUnitVisible()) {
            switch (mUnitPosition) {
                case TOP:
                case BOTTOM:
                    scaleX = 1.1f; // scaleX square to rectangle, so the longer text with unit fits better
                    scaleY = 0.88f;
                    break;
                case LEFT_TOP:
                case RIGHT_TOP:
                case LEFT_BOTTOM:
                case RIGHT_BOTTOM:
                    scaleX = 0.77f; // scaleX square to rectangle, so the longer text with unit fits better
                    scaleY = 1.33f;
                    break;
            }

        }
        //remember type casted to in for left,right,top,bottom
        return new RectFloat((int) (_circleBounds.left + (widthDelta * scaleX)), (int) (_circleBounds.top + (widthDelta * scaleY)), (int) (_circleBounds.right - (widthDelta * scaleX)), (int) (_circleBounds.bottom - (widthDelta * scaleY)));

    }

    public boolean isUnitVisible() {
        return mShowUnit;
    }

    /**
     * Scale factor for main text in the center of the circle view.
     * Only used if auto text size is enabled.
     *
     * @param _textScale The scale value.
     */
    public void setTextScale(float _textScale) {
        mTextScale = _textScale;
    }


    public void setUnit(String _unit) {
        if (_unit == null) {
            mUnit = "";
        } else {
            mUnit = _unit;
        }
        invalidate();
    }

    public boolean getRoundToWholeNumber() {
        return mRoundToWholeNumber;
    }

    public void setRoundToWholeNumber(boolean roundToWholeNumber) {
        mRoundToWholeNumber = roundToWholeNumber;
    }

    public boolean getRoundToBlock() {
        return mRoundToBlock;
    }

    public void setRoundToBlock(boolean _roundToBlock) {
        mRoundToBlock = _roundToBlock;
    }

    /**
     * The max value allowed of the progress bar. Used to limit the max possible value of the current value.
     *
     * @param _maxValueAllowed The max value allowed.
     */
    public void setMaxValueAllowed(float _maxValueAllowed) {
        mMaxValueAllowed = _maxValueAllowed;
    }


    /**
     * The min value allowed of the progress bar. Used to limit the min possible value of the current value.
     *
     * @param _minValueAllowed The min value allowed.
     */
    public void setMinValueAllowed(float _minValueAllowed) {
        mMinValueAllowed = _minValueAllowed;
    }

    public float getInnerContourSize() {
        return mInnerContourSize;
    }


    public void setInnerContourSize(float _contourSize) {
        mInnerContourSize = _contourSize;
        mInnerContourPaint.setStrokeWidth(_contourSize);
    }

    public int getInnerContourColor() {
        return mInnerContourColor;
    }


    public void setInnerContourColor(int _contourColor) {
        mInnerContourColor = _contourColor;
        mInnerContourPaint.setColor(new Color(_contourColor));
    }

    public float getOuterContourSize() {
        return mOuterContourSize;
    }


    public void setOuterContourSize(float _contourSize) {
        mOuterContourSize = _contourSize;
        mOuterContourPaint.setStrokeWidth(_contourSize);
    }

    public int getOuterContourColor() {
        return mOuterContourColor;
    }


    public void setOuterContourColor(int _contourColor) {
        mOuterContourColor = _contourColor;
        mOuterContourPaint.setColor(new Color(_contourColor));
        setupBounds();
    }


    public int getDelayMillis() {
        return mFrameDelayMillis;
    }

    public void setDelayMillis(int delayMillis) {
        if (delayMillis < 1) {
            this.mFrameDelayMillis = 5;
        } else {
            this.mFrameDelayMillis = delayMillis;
        }
    }

    public int getFillColor() {
        return mBackgroundCirclePaint.getColor().getValue();
    }

    public float getCurrentValue() {
        return mCurrentValue;
    }

    public float getMinValueAllowed() {
        return mMinValueAllowed;
    }

    public float getMaxValueAllowed() {
        return mMaxValueAllowed;
    }

    public float getRelativeUniteSize() {
        return mRelativeUniteSize;
    }

    public int getRimColor() {
        return mRimColor;
    }

    public Shader getRimShader() {
        return mRimPaint.getShader();
    }

    public void setRimShader(Shader shader) {
        this.mRimPaint.setShader(shader, Paint.ShaderType.LINEAR_SHADER);
    }

    public int getRimWidth() {
        return mRimWidth;
    }

    public float getSpinSpeed() {
        return mSpinSpeed;
    }

    public Paint.StrokeCap getSpinnerStrokeCap() {
        return mSpinnerStrokeCap;
    }

    public int getStartAngle() {
        return mStartAngle;
    }

    public float getTextScale() {
        return mTextScale;
    }

    public int getTextSize() {
        return mTextSize;
    }

    public String getUnit() {
        return mUnit;
    }


    public float getUnitScale() {
        return mUnitScale;
    }

    public int getUnitSize() {
        return mUnitTextSize;
    }

    public boolean isAutoTextSize() {
        return mIsAutoTextSize;
    }


    public boolean isSeekModeEnabled() {
        return mSeekModeEnabled;
    }


    public boolean isShowBlock() {
        return mShowBlock;
    }

    public void setShowBlock(boolean showBlock) {
        mShowBlock = showBlock;
    }

    public boolean isShowTextWhileSpinning() {
        return mShowTextWhileSpinning;
    }

    public void setBarColor(int... barColors) {
        this.mBarColors = barColors;
        setupBarPaint();
    }


    public void setOnAnimationStateChangedListener(AnimationStateChangedListener _animationStateChangedListener) {
        mAnimationStateChangedListener = _animationStateChangedListener;
    }

    public void setOnProgressChangedListener(OnProgressChangedListener listener) {
        onProgressChangedListener = listener;
    }

    /**
     * Sets the value of the circle view with an animation.
     * The current value is used as the start value of the animation
     *
     * @param _valueTo value after animation
     */
    public void setValueAnimated(float _valueTo) {
        setValueAnimated(_valueTo, 1200);
    }

    /**
     * Sets the value of the circle view with an animation.
     * The current value is used as the start value of the animation
     *
     * @param _valueTo           value after animation
     * @param _animationDuration the duration of the animation in milliseconds.
     */
    public void setValueAnimated(float _valueTo, long _animationDuration) {
        setValueAnimated(mCurrentValue, _valueTo, _animationDuration);
    }

    /**
     * Sets the value of the circle view with an animation.
     *
     * @param _valueFrom         start value of the animation
     * @param _valueTo           value after animation
     * @param _animationDuration the duration of the animation in milliseconds
     */
    public void setValueAnimated(float _valueFrom, float _valueTo, long _animationDuration) {
        // round to block
        if (mShowBlock && mRoundToBlock) {
            float value_per_block = mMaxValue / (float) mBlockCount;
            _valueTo = Math.round(_valueTo / value_per_block) * value_per_block;

        } else if (mRoundToWholeNumber) {
            _valueTo = Math.round(_valueTo);
        }

        // respect min and max values allowed
        _valueTo = Math.max(mMinValueAllowed, _valueTo);

        if (mMaxValueAllowed >= 0)
            _valueTo = Math.min(mMaxValueAllowed, _valueTo);

        mAnimationDuration = _animationDuration;
        InnerEvent innerEvent = InnerEvent.get(AnimationMsg.SET_VALUE_ANIMATED.ordinal());
        innerEvent.object = new float[]{_valueFrom, _valueTo};
        mAnimationHandler.sendEvent(innerEvent);
        triggerOnProgressChanged(_valueTo);
    }


    public DecimalFormat getDecimalFormat() {
        return decimalFormat;
    }

    public void setDecimalFormat(DecimalFormat decimalFormat) {
        if (decimalFormat == null) {
            throw new IllegalArgumentException("decimalFormat must not be null!");
        }
        this.decimalFormat = decimalFormat;
    }

    /**
     * Sets interpolator for value animations.
     *
     * @param interpolator the interpolator
     */
    public void setValueInterpolator(TimeCurveType interpolator) {
        mAnimationHandler.setValueInterpolator(interpolator);
    }


    /**
     * Sets the interpolator for length changes of the bar.
     *
     * @param interpolator the interpolator
     */
    public void setLengthChangeInterpolator(TimeCurveType interpolator) {
        mAnimationHandler.setLengthChangeInterpolator(interpolator);
    }


    public void setRimColor(int rimColor) {
        mRimColor = rimColor;
        mRimPaint.setColor(new Color(rimColor));
    }

    /**
     * Sets the unit text color.
     * Also sets {@link #setTextColorAuto(boolean)} to false
     *
     * @param unitColor The color.
     */
    public void setUnitColor(int unitColor) {
        mUnitColor = unitColor;
        mUnitTextPaint.setColor(new Color(unitColor));
        mIsAutoColorEnabled = false;
    }

    public void setTextColor(int textColor) {
        mTextColor = textColor;
        mTextPaint.setColor(new Color(textColor));
    }

    /**
     * Text size of the unit string. Only used if text size is also set. (So automatic text size
     * calculation is off. see {@link #setTextSize(int)}).
     * If auto text size is on, use to scale unit size.
     *
     * @param unitSize The text size of the unit.
     */
    public void setUnitSize(int unitSize) {
        mUnitTextSize = unitSize;
        mUnitTextPaint.setTextSize(unitSize);
    }

    /**
     * Text size of the text string. Disables auto text size
     * If auto text size is on, use to scale textSize.
     *
     * @param textSize The text size of the unit.
     */
    public void setTextSize(int textSize) {
        this.mTextPaint.setTextSize(textSize);
        mTextSize = textSize;
        mIsAutoTextSize = false;
    }


    public void setSpinBarColor(int _color) {
        mSpinnerColor = _color;
        mBarSpinnerPaint.setColor(new Color(mSpinnerColor));
    }

    /**
     * Allows to add a line to the start/end of the bar
     *
     * @param _barWidth        The width of the stroke on the start/end of the bar in pixel.
     * @param _barStartEndLine The type of line on the start/end of the bar.
     * @param _lineColor       The line color
     * @param _sweepWidth      The sweep amount in degrees for the start and end bars to cover.
     */
    public void setBarStartEndLine(int _barWidth, BarStartEndLine _barStartEndLine, int _lineColor, float _sweepWidth) {
        mBarStartEndLineWidth = _barWidth;
        mBarStartEndLine = _barStartEndLine;
        mBarStartEndLineColor = _lineColor;
        mBarStartEndLineSweep = _sweepWidth;
        setupPaints();
    }

    /**
     * The max value of the progress bar. Used to calculate the percentage of the current value.
     * The bar fills according to the percentage. The default value is 100.
     *
     * @param _maxValue The max value.
     */
    public void setMaxValue(float _maxValue) {
        mMaxValue = _maxValue;
    }

    public float getMaxValue() {
        return mMaxValue;
    }


    public void setUnitToTextScale(float _relativeUniteSize) {
        mRelativeUniteSize = _relativeUniteSize;
        triggerReCalcTextSizesAndPositions();
    }

    /**
     * Set the text in the middle of the circle view.
     * You need also set the {@link TextMode} to TextMode.TEXT to see the text.
     *
     * @param text The text to show
     */
    public void setText(String text) {
        mText = text != null ? text : "";
        invalidate();
    }

    public void setUnitPosition(UnitPosition _unitPosition) {
        mUnitPosition = _unitPosition;
        triggerReCalcTextSizesAndPositions(); // triggers recalculating text sizes
    }

    /**
     * Sets the auto text mode.
     *
     * @param _textValue The mode
     */
    public void setTextMode(TextMode _textValue) {
        mTextMode = _textValue;
    }


    public void setAutoTextSize(boolean _autoTextSize) {
        mIsAutoTextSize = _autoTextSize;
    }

    /**
     * If auto text color is enabled, the text color  and the unit color is always the same as the rim color.
     * This is useful if the rim has multiple colors (color gradient), than the text will always have
     * the color of the tip of the rim.
     *
     * @param isEnabled true to enable, false to disable
     */
    public void setTextColorAuto(boolean isEnabled) {
        mIsAutoColorEnabled = isEnabled;
    }

    /**
     * Length of spinning bar in degree.
     *
     * @param barLength length in degree
     */
    public void setSpinningBarLength(float barLength) {
        this.mSpinningBarLengthCurrent = mSpinningBarLengthOrig = barLength;
    }


    public void setBarStrokeCap(Paint.StrokeCap _barStrokeCap) {
        mBarStrokeCap = _barStrokeCap;
        mBarPaint.setStrokeCap(_barStrokeCap);
        if (mBarStrokeCap != Paint.StrokeCap.BUTT_CAP) {
            mShaderlessBarPaint = new Paint(mBarPaint);
            mShaderlessBarPaint.setShader(null, Paint.ShaderType.RADIAL_SHADER);
            mShaderlessBarPaint.setColor(new Color(mBarColors[0]));
        }
    }

    /**
     * Set the value of the circle view without an animation.
     * Stops any currently active animations.
     *
     * @param _value The value.
     */
    public void setValue(float _value) {
        // round to block
        if (mShowBlock && mRoundToBlock) {
            float value_per_block = mMaxValue / (float) mBlockCount;
            _value = Math.round(_value / value_per_block) * value_per_block;

        } else if (mRoundToWholeNumber) { // round to whole number
            _value = Math.round(_value);
        }

        // respect min and max values allowed
        _value = Math.max(mMinValueAllowed, _value);

        if (mMaxValueAllowed >= 0)
            _value = Math.min(mMaxValueAllowed, _value);

        InnerEvent innerEvent = InnerEvent.get(AnimationMsg.SET_VALUE.ordinal());
        innerEvent.eventId = AnimationMsg.SET_VALUE.ordinal();
        innerEvent.object = new float[]{_value, _value};
        mAnimationHandler.sendEvent(innerEvent);
        triggerOnProgressChanged(_value);
    }

    private void triggerOnProgressChanged(float value) {
        if (onProgressChangedListener != null && value != previousProgressChangedValue) {
            onProgressChangedListener.onProgressChanged(value);
            previousProgressChangedValue = value;
        }
    }

    /**
     * Sets the background color of the entire Progress Circle.
     * Set the color to 0x00000000 (Color.TRANSPARENT) to hide it.
     *
     * @param circleColor the color.
     */
    public void setFillCircleColor(int circleColor) {
        mBackgroundCircleColor = circleColor;
        mBackgroundCirclePaint.setColor(new Color(circleColor));
        setupBackgroundCirclePaint();
    }


    public void setDirection(Direction direction) {
        mDirection = direction;
    }

    private void setSpin(boolean spin) {
        mSpin = spin;
    }

    /**
     * The amount of degree to move the bar on every draw call.
     *
     * @param spinSpeed the speed of the spinner
     */
    public void setSpinSpeed(float spinSpeed) {
        mSpinSpeed = spinSpeed;
    }


    public void setRimWidth(int rimWidth) {
        mRimWidth = rimWidth;
        mRimPaint.setStrokeWidth(rimWidth);
    }

    public int getBarWidth() {
        return mBarWidth;
    }


    public void setBarWidth(int barWidth) {
        this.mBarWidth = barWidth;
        mBarPaint.setStrokeWidth(barWidth);
        mBarSpinnerPaint.setStrokeWidth(barWidth);
    }

    @Override
    public void onRefreshed(Component component) {
        onMeasure(component.getWidth(), component.getHeight());
    }

    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int size;
        int width = widthMeasureSpec;
        int height = heightMeasureSpec;
        int widthWithoutPadding = width - getPaddingLeft() - getPaddingRight();
        int heightWithoutPadding = height - getPaddingTop() - getPaddingBottom();

        if (widthWithoutPadding > heightWithoutPadding) {
            size = heightWithoutPadding;
        } else {
            size = widthWithoutPadding;
        }

        setComponentSize(size + getPaddingLeft() + getPaddingRight(), size + getPaddingTop() + getPaddingBottom());
    }

    protected void onSizeChanged() {

        // Share the dimensions
        mLayoutWidth = getWidth();
        mLayoutHeight = getHeight();

        setupBounds();
        setupBarPaint();

        if (mClippingBitmap != null) {
            PixelMap.InitializationOptions initializationOptions = new PixelMap.InitializationOptions();

            initializationOptions.size = new Size(getWidth(), getHeight());
            initializationOptions.pixelFormat = PixelFormat.ARGB_8888;
            mClippingBitmap = PixelMap.create(mClippingBitmap, initializationOptions);

        }

        invalidate();
    }

    private void setupBounds() {
        // Width should equal to Height, find the min value to setup the circle
        int minValue = Math.min(mLayoutWidth, mLayoutHeight);

        // Calc the Offset if needed
        int xOffset = mLayoutWidth - minValue;
        int yOffset = mLayoutHeight - minValue;

        // Add the offset
        float paddingTop = this.getPaddingTop() + (yOffset / 2);
        float paddingBottom = this.getPaddingBottom() + (yOffset / 2);
        float paddingLeft = this.getPaddingLeft() + (xOffset / 2);
        float paddingRight = this.getPaddingRight() + (xOffset / 2);

        int width = getWidth(); //this.getLayoutParams().width;
        int height = getHeight(); //this.getLayoutParams().height;

        float circleWidthHalf = mBarWidth / 2f > mRimWidth / 2f + mOuterContourSize ? mBarWidth / 2f : mRimWidth / 2f + mOuterContourSize;

        mCircleBounds = new RectFloat(paddingLeft + circleWidthHalf,
                paddingTop + circleWidthHalf,
                width - paddingRight - circleWidthHalf,
                height - paddingBottom - circleWidthHalf);


        mInnerCircleBound = new RectFloat(paddingLeft + (mBarWidth),
                paddingTop + (mBarWidth),
                width - paddingRight - (mBarWidth),
                height - paddingBottom - (mBarWidth));
        mOuterTextBounds = getInnerCircleRect(mCircleBounds);
        mCircleInnerContour = new RectFloat(mCircleBounds.left + (mRimWidth / 2.0f) + (mInnerContourSize / 2.0f), mCircleBounds.top + (mRimWidth / 2.0f) + (mInnerContourSize / 2.0f), mCircleBounds.right - (mRimWidth / 2.0f) - (mInnerContourSize / 2.0f), mCircleBounds.bottom - (mRimWidth / 2.0f) - (mInnerContourSize / 2.0f));
        mCircleOuterContour = new RectFloat(mCircleBounds.left - (mRimWidth / 2.0f) - (mOuterContourSize / 2.0f), mCircleBounds.top - (mRimWidth / 2.0f) - (mOuterContourSize / 2.0f), mCircleBounds.right + (mRimWidth / 2.0f) + (mOuterContourSize / 2.0f), mCircleBounds.bottom + (mRimWidth / 2.0f) + (mOuterContourSize / 2.0f));

        mCenter = new CvPoint((int) mCircleBounds.getCenter().getPointX(), (int) mCircleBounds.getCenter().getPointY());
    }


    @Override
    public boolean onTouchEvent(Component component, TouchEvent touchEvent) {
        if (!mSeekModeEnabled) {
            return false;
        }
        int action = touchEvent.getAction();
        int activePointerIndex = touchEvent.getIndex();
        MmiPoint point = touchEvent.getPointerPosition(activePointerIndex);
        float x = point.getX();
        float y = point.getY();
        switch (touchEvent.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
            case TouchEvent.PRIMARY_POINT_UP: {
                mTouchEventCount = 0;
                CvPoint point1 = new CvPoint((int) x, (int) y);
                float angle = getRotationAngleForPointFromStart(point1);
                setValueAnimated(mMaxValue / 360f * angle, 800);
                return true;
            }
            case TouchEvent.POINT_MOVE: {
                mTouchEventCount++;
                if (mTouchEventCount > 5) { //touch/move guard
                    CvPoint point1 = new CvPoint((int) x, (int) y);
                    float angle = getRotationAngleForPointFromStart(point1);
                    setValue(mMaxValue / 360f * angle);
                    return true;
                } else {
                    return false;
                }

            }
            case TouchEvent.CANCEL:
                mTouchEventCount = 0;
                return false;
        }
        return false;
    }


    public interface OnProgressChangedListener {
        void onProgressChanged(float value);
    }

    private float calcTextSizeForCircle(String _text, Paint _textPaint, RectFloat _circleBounds) {

        //get mActualTextBounds bounds
        RectFloat innerCircleBounds = getInnerCircleRect(_circleBounds);
        return calcTextSizeForRect(_text, _textPaint, innerCircleBounds);

    }


    private void setupBarPaint() {
        if (mBarColor.length > 1) {
            mBarPaint.setShader(new SweepShader(mCircleBounds.getCenter().getPointX(), mCircleBounds.getCenter().getPointY(), mBarColor, null), Paint.ShaderType.SWEEP_SHADER);
            Matrix matrix = new Matrix();
            mBarPaint.getShader();

            matrix.postTranslate(-mCircleBounds.getCenter().getPointX(), -mCircleBounds.getCenter().getPointY());
            matrix.postRotate(mStartAngle);
            matrix.postTranslate(mCircleBounds.getCenter().getPointX(), mCircleBounds.getCenter().getPointY());
            mBarPaint.setColor(new Color(mBarColors[0]));
        } else if (mBarColor.length == 1) {
            mBarPaint.setColor(new Color(mBarColors[0]));
            mBarPaint.setShader(null, Paint.ShaderType.LINEAR_SHADER);
        } else {
            mBarPaint.setColor(new Color(mBarColorStandard));
            mBarPaint.setShader(null, Paint.ShaderType.LINEAR_SHADER);
        }

        mBarPaint.setAntiAlias(true);
        mBarPaint.setStrokeCap(mBarStrokeCap);
        mBarPaint.setStyle(Paint.Style.STROKE_STYLE);
        mBarPaint.setStrokeWidth(mBarWidth);

        if (mBarStrokeCap != Paint.StrokeCap.BUTT_CAP) {
            mShaderlessBarPaint = new Paint(mBarPaint);
            mShaderlessBarPaint.setColor(new Color(mBarColors[0]));
        }
    }

    /**
     * Setup all paints.
     * Call only if changes to color or size properties are not visible.
     */
    public void setupPaints() {
        setupBarPaint();
        setupBarSpinnerPaint();
        setupOuterContourPaint();
        setupInnerContourPaint();
        setupUnitTextPaint();
        setupTextPaint();
        setupBackgroundCirclePaint();
        setupRimPaint();
        setupBarStartEndLinePaint();
    }

    private void setupBarStartEndLinePaint() {
        mBarStartEndLinePaint.setColor(new Color(mBarStartEndLineColor));
        mBarStartEndLinePaint.setAntiAlias(true);
        mBarStartEndLinePaint.setStyle(Paint.Style.STROKE_STYLE);
        mBarStartEndLinePaint.setStrokeWidth(mBarStartEndLineWidth);
    }

    private void setupOuterContourPaint() {
        mOuterContourPaint.setColor(new Color(mOuterContourColor));
        mOuterContourPaint.setAntiAlias(true);
        mOuterContourPaint.setStyle(Paint.Style.STROKE_STYLE);
        mOuterContourPaint.setStrokeWidth(mOuterContourSize);
    }

    private void setupInnerContourPaint() {
        mInnerContourPaint.setColor(new Color(mInnerContourColor));
        mInnerContourPaint.setAntiAlias(true);
        mInnerContourPaint.setStyle(Paint.Style.STROKE_STYLE);
        mInnerContourPaint.setStrokeWidth(mInnerContourSize);
    }

    private void setupUnitTextPaint() {
        mUnitTextPaint.setStyle(Paint.Style.FILL_STYLE);
        mUnitTextPaint.setAntiAlias(true);
        if (unitTextTypeface != null) {
            mUnitTextPaint.setFont(unitTextTypeface);
        }
    }

    private void setupTextPaint() {
        mTextPaint.setSubpixelAntiAlias(true);
        mTextPaint.setUnderLine(false);
        mTextPaint.setFont(Font.MONOSPACE);
        mTextPaint.setColor(new Color(mTextColor));
        mTextPaint.setStyle(Paint.Style.FILL_STYLE);
        mTextPaint.setAntiAlias(true);
        mTextPaint.setTextSize(mTextSize);
        if (textTypeface != null) {
            mTextPaint.setFont(Font.DEFAULT_BOLD);
        } else {
            mTextPaint.setFont(Font.MONOSPACE);
        }

    }

    /**
     * Set or clear the typeface object.
     * <p />
     * Pass null to clear any previous typeface.
     * As a convenience, the parameter passed is also returned.
     *
     * @param typeface May be null. The typeface to be installed in the paint
     * @return         typeface
     */
    public void setTextTypeface(Font typeface) {
       mTextPaint.setFont(typeface);
    }

    public void setUnitTextTypeface(Font typeface) {
        mUnitTextPaint.setFont(typeface);
    }

    /**
     * @param _spinnerStrokeCap The stroke cap of the progress bar in spinning mode.
     */
    public void setSpinnerStrokeCap(Paint.StrokeCap _spinnerStrokeCap) {
        mSpinnerStrokeCap = _spinnerStrokeCap;
        mBarSpinnerPaint.setStrokeCap(_spinnerStrokeCap);
    }


    private void setupBackgroundCirclePaint() {
        mBackgroundCirclePaint.setColor(new Color(mBackgroundCircleColor));
        mBackgroundCirclePaint.setAntiAlias(true);
        mBackgroundCirclePaint.setStyle(Paint.Style.FILL_STYLE);
    }

    private void setupRimPaint() {
        mRimPaint.setColor(new Color(mRimColor));
        mRimPaint.setAntiAlias(true);
        mRimPaint.setStyle(Paint.Style.STROKE_STYLE);
        mRimPaint.setStrokeWidth(mRimWidth);
    }

    private void setupBarSpinnerPaint() {
        mBarSpinnerPaint.setAntiAlias(true);
        mBarSpinnerPaint.setStrokeCap(mSpinnerStrokeCap);
        mBarSpinnerPaint.setStyle(Paint.Style.STROKE_STYLE);
        mBarSpinnerPaint.setStrokeWidth(mBarWidth);
        mBarSpinnerPaint.setColor(new Color(mSpinnerColor));
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {

        arc.setArc(360, 360, false);
        if (DEBUG) {
            drawDebug(canvas);
        }

        float degrees = (360f / mMaxValue * mCurrentValue);

        // Draw the background circle
        if (mBackgroundCircleColor != 0) {
            canvas.drawArc(mInnerCircleBound, arc, mBackgroundCirclePaint);
        }

        //Draw the rim
        if (mRimWidth > 0) {
            if (!mShowBlock) {

                canvas.drawArc(mCircleBounds, arc, mRimPaint);
            } else {
                drawBlocks(canvas, mCircleBounds, mStartAngle, 360, false, mRimPaint);
            }
        }

        //Draw outer contour
        if (mOuterContourSize > 0) {
            arc.setArc(360, 360, false);
            canvas.drawArc(mCircleOuterContour, arc, mOuterContourPaint);
        }

        //Draw outer contour
        if (mInnerContourSize > 0) {
            arc.setArc(360, 360, false);
            canvas.drawArc(mCircleInnerContour, arc, mInnerContourPaint);
        }

        //Draw spinner
        if (mAnimationState == AnimationState.SPINNING || mAnimationState == AnimationState.END_SPINNING) {
            drawSpinner(canvas);
            if (mShowTextWhileSpinning) {
                drawTextWithUnit(canvas);
            }

        } else if (mAnimationState == AnimationState.END_SPINNING_START_ANIMATING) {
            //draw spinning arc
            drawSpinner(canvas);

            if (mDrawBarWhileSpinning) {
                drawBar(canvas, degrees);
                drawTextWithUnit(canvas);
            } else if (mShowTextWhileSpinning) {
                drawTextWithUnit(canvas);
            }

        } else {
            drawBar(canvas, degrees);
            drawTextWithUnit(canvas);
        }

        if (mClippingBitmap != null) {
            canvas.drawPixelMapHolder(new PixelMapHolder(mClippingBitmap), 0, 0, mMaskPaint);
        }

        if (mBarStartEndLineWidth > 0 && mBarStartEndLine != BarStartEndLine.NONE) {
            drawStartEndLine(canvas, degrees);
        }

    }

    private void drawStartEndLine(Canvas _canvas, float _degrees) {
        if (_degrees == 0f)
            return;

        float startAngle = mDirection == Direction.CW ? mStartAngle : mStartAngle - _degrees;

        startAngle -= mBarStartEndLineSweep / 2f;

        if (mBarStartEndLine == BarStartEndLine.START || mBarStartEndLine == BarStartEndLine.BOTH) {

            arc.setArc(startAngle, mBarStartEndLineSweep, false);
            _canvas.drawArc(mCircleBounds, arc, mBarStartEndLinePaint);
        }

        if (mBarStartEndLine == BarStartEndLine.END || mBarStartEndLine == BarStartEndLine.BOTH) {
            arc.setArc(startAngle + _degrees, mBarStartEndLineSweep, false);
            _canvas.drawArc(mCircleBounds, arc, mBarStartEndLinePaint);
        }
    }

    private void drawBlocks(Canvas _canvas, RectFloat circleBounds, float startAngle, float _degrees, boolean userCenter, Paint paint) {
        float tmpDegree = 0.0f;
        while (tmpDegree < _degrees) {
            arc.setArc(startAngle + tmpDegree, Math.min(mBlockScaleDegree, _degrees - tmpDegree), userCenter);
            _canvas.drawArc(circleBounds, arc, paint);
            tmpDegree += mBlockDegree;
        }
    }

    private void drawBar(Canvas _canvas, float _degrees) {
        float startAngle = mDirection == Direction.CW ? mStartAngle : mStartAngle - _degrees;
        if (!mShowBlock) {

            if (mBarStrokeCap != Paint.StrokeCap.BUTT_CAP && _degrees > 0 && mBarColors.length > 1) {
                if (_degrees > 180) {
                    arc.setArc(startAngle, _degrees / 2, false);
                    _canvas.drawArc(mCircleBounds, arc, mBarPaint);
                    arc.setArc(startAngle, 1, false);
                    _canvas.drawArc(mCircleBounds, arc, mShaderlessBarPaint);
                    arc.setArc(startAngle + (_degrees / 2), _degrees / 2, false);
                    _canvas.drawArc(mCircleBounds, arc, mBarPaint);
                } else {
                    arc.setArc(startAngle, _degrees, false);
                    _canvas.drawArc(mCircleBounds, arc, mBarPaint);
                    arc.setArc(startAngle, 1, false);
                    _canvas.drawArc(mCircleBounds, arc, mShaderlessBarPaint);
                }

            } else {
                arc.setArc(startAngle, _degrees, false);
                _canvas.drawArc(mCircleBounds, arc, mBarPaint);
            }
        } else {
            drawBlocks(_canvas, mCircleBounds, startAngle, _degrees, false, mBarPaint);
        }
    }

    private void drawDebug(Canvas canvas) {
        Paint innerRectPaint = new Paint();
        innerRectPaint.setColor(Color.YELLOW);
        canvas.drawRect(mCircleBounds, innerRectPaint);
    }

    private void drawSpinner(Canvas canvas) {
        if (mSpinningBarLengthCurrent < 0) {
            mSpinningBarLengthCurrent = 1;
        }

        float startAngle;
        if (mDirection == Direction.CW) {
            startAngle = mStartAngle + mCurrentSpinnerDegreeValue - mSpinningBarLengthCurrent;
        } else {
            startAngle = mStartAngle - mCurrentSpinnerDegreeValue;
        }
        arc.setArc(startAngle, mSpinningBarLengthCurrent, false);
        canvas.drawArc(mCircleBounds, arc,
                mBarSpinnerPaint);
    }

    private void drawTextWithUnit(Canvas canvas) {

        final float relativeGapHeight;
        final float relativeGapWidth;
        final float relativeHeight;
        final float relativeWidth;

        switch (mUnitPosition) {
            case TOP:
            case BOTTOM:
                relativeGapWidth = 0.05f; //gap size between text and unit
                relativeGapHeight = 0.025f; //gap size between text and unit
                relativeHeight = 0.25f * mRelativeUniteSize;
                relativeWidth = 0.4f * mRelativeUniteSize;
                break;
            default:
            case LEFT_TOP:
            case RIGHT_TOP:
            case LEFT_BOTTOM:
            case RIGHT_BOTTOM:
                relativeGapWidth = 0.05f; //gap size between text and unit
                relativeGapHeight = 0.025f; //gap size between text and unit
                relativeHeight = 0.55f * mRelativeUniteSize;
                relativeWidth = 0.3f * mRelativeUniteSize;
                break;
        }

        float unitGapWidthHalf = mOuterTextBounds.getWidth() * relativeGapWidth / 2f;
        float unitWidth = (mOuterTextBounds.getWidth() * relativeWidth);

        float unitGapHeightHalf = mOuterTextBounds.getHeight() * relativeGapHeight / 2f;
        float unitHeight = (mOuterTextBounds.getHeight() * relativeHeight);


        boolean update = false;
        //Draw Text
        if (mIsAutoColorEnabled) {
            mTextPaint.setColor(new Color(calcTextColor(mCurrentValue)));
        }

        //set text
        String text;
        switch (mTextMode) {
            case TEXT:
            default:
                text = mText != null ? mText : "";
                break;
            case PERCENT:
                text = decimalFormat.format(100f / mMaxValue * mCurrentValue);
                break;
            case VALUE:
                text = decimalFormat.format(mCurrentValue);
                break;
        }


        // only re-calc position and size if string length changed
        if (mTextLength != text.length()) {

            update = true;
            mTextLength = text.length();
            if (mTextLength == 1) {
                mOuterTextBounds = getInnerCircleRect(mCircleBounds);

                mOuterTextBounds = new RectFloat(mOuterTextBounds.left + (mOuterTextBounds.getWidth() * 0.1f), mOuterTextBounds.top, mOuterTextBounds.right - (mOuterTextBounds.getWidth() * 0.1f), mOuterTextBounds.bottom);
            } else {
                mOuterTextBounds = getInnerCircleRect(mCircleBounds);
            }
            if (mIsAutoTextSize) {
                setTextSizeAndTextBoundsWithAutoTextSize(unitGapWidthHalf, unitWidth, unitGapHeightHalf, unitHeight, text);
            } else {
                setTextSizeAndTextBoundsWithFixedTextSize(text);
            }
        }

        if (DEBUG) {
            Paint rectPaint = new Paint();
            rectPaint.setColor(Color.MAGENTA);
            canvas.drawRect(mOuterTextBounds, rectPaint);
            rectPaint.setColor(Color.GREEN);
            canvas.drawRect(mActualTextBounds, rectPaint);

        }

        canvas.drawText(mTextPaint, text, mActualTextBounds.left - (mTextPaint.getTextSize() * 0.02f), mActualTextBounds.bottom);

        if (mShowUnit) {

            if (mIsAutoColorEnabled) {
                mUnitTextPaint.setColor(new Color(calcTextColor(mCurrentValue)));
            }
            if (update) {
                //calc unit text position
                if (mIsAutoTextSize) {
                    setUnitTextBoundsAndSizeWithAutoTextSize(unitGapWidthHalf, unitWidth, unitGapHeightHalf, unitHeight);

                } else {
                    setUnitTextBoundsAndSizeWithFixedTextSize(unitGapWidthHalf * 2f, unitGapHeightHalf * 2f);
                }
            }

            if (DEBUG) {
                Paint rectPaint = new Paint();
                rectPaint.setColor(Color.BLUE);
                canvas.drawRect(mUnitBounds, rectPaint);
            }

            canvas.drawText(mUnitTextPaint, mUnit, mUnitBounds.left - (mUnitTextPaint.getTextSize() * 0.02f), mUnitBounds.bottom);
        }
    }


    private void setTextSizeAndTextBoundsWithFixedTextSize(String text) {
        mTextPaint.setTextSize(mTextSize);
        mActualTextBounds = calcTextBounds(text, mTextPaint, mCircleBounds); //center text in circle
    }

    private void setUnitTextBoundsAndSizeWithAutoTextSize(float unitGapWidthHalf, float unitWidth, float unitGapHeightHalf, float unitHeight) {
        //calc the rectangle containing the unit text
        switch (mUnitPosition) {

            case TOP: {
                mUnitBounds = new RectFloat(mOuterTextBounds.left, mOuterTextBounds.top, mOuterTextBounds.right, mOuterTextBounds.top + unitHeight - unitGapHeightHalf);
                break;
            }
            case BOTTOM:
                mUnitBounds = new RectFloat(mOuterTextBounds.left, mOuterTextBounds.bottom - unitHeight + unitGapHeightHalf, mOuterTextBounds.right, mOuterTextBounds.bottom);
                break;
            case LEFT_TOP:
            case LEFT_BOTTOM: {
                mUnitBounds = new RectFloat(mOuterTextBounds.left, mOuterTextBounds.top, mOuterTextBounds.left + unitWidth - unitGapWidthHalf, mOuterTextBounds.top + unitHeight);
                break;
            }
            case RIGHT_TOP:
            case RIGHT_BOTTOM:
            default: {
                mUnitBounds = new RectFloat(mOuterTextBounds.right - unitWidth + unitGapWidthHalf, mOuterTextBounds.top, mOuterTextBounds.right, mOuterTextBounds.top + unitHeight);
            }
            break;
        }

        mUnitTextPaint.setTextSize((int) (calcTextSizeForRect(mUnit, mUnitTextPaint, mUnitBounds) * mUnitScale));
        mUnitBounds = calcTextBounds(mUnit, mUnitTextPaint, mUnitBounds); // center text in rectangle and reuse it

        switch (mUnitPosition) {
            case LEFT_TOP:
            case RIGHT_TOP: {
                //move unite to top of text
                float dy = mActualTextBounds.top - mUnitBounds.top;
                mUnitBounds.translate(0, dy);
                break;
            }
            case LEFT_BOTTOM:
            case RIGHT_BOTTOM: {
                //move unite to bottom of text
                float dy = mActualTextBounds.bottom - mUnitBounds.bottom;
                mUnitBounds.translate(0, dy);
                break;
            }
        }
    }

    private void setUnitTextBoundsAndSizeWithFixedTextSize(float unitGapWidth, float unitGapHeight) {
        mUnitTextPaint.setTextSize(mUnitTextSize);
        mUnitBounds = calcTextBounds(mUnit, mUnitTextPaint, mOuterTextBounds); // center text in rectangle and reuse it

        switch (mUnitPosition) {

            case TOP:
                mUnitBounds.translateTo(mUnitBounds.left, mActualTextBounds.top - unitGapHeight - mUnitBounds.getHeight());
                break;
            case BOTTOM:
                mUnitBounds.translateTo(mUnitBounds.left, mActualTextBounds.bottom + unitGapHeight);
                break;
            case LEFT_TOP:
            case LEFT_BOTTOM:
                mUnitBounds.translateTo(mActualTextBounds.left - unitGapWidth - mUnitBounds.getWidth(), mUnitBounds.top);
                break;
            case RIGHT_TOP:
            case RIGHT_BOTTOM:
            default:
                mUnitBounds.translateTo(mActualTextBounds.right + unitGapWidth, mUnitBounds.top);
                break;
        }

        switch (mUnitPosition) {
            case LEFT_TOP:
            case RIGHT_TOP: {
                //move unite to top of text
                float dy = mActualTextBounds.top - mUnitBounds.top;
                mUnitBounds.translate(0, dy);
                break;
            }
            case LEFT_BOTTOM:
            case RIGHT_BOTTOM: {
                //move unite to bottom of text
                float dy = mActualTextBounds.bottom - mUnitBounds.bottom;
                mUnitBounds.translate(0, dy);
                break;
            }
        }
    }

    private int calcTextColor(double value) {
        if (mBarColors.length > 1) {
            double percent = 1f / getMaxValue() * value;
            int low = (int) Math.floor((mBarColors.length - 1) * percent);
            int high = low + 1;
            if (low < 0) {
                low = 0;
                high = 1;
            } else if (high >= mBarColors.length) {
                low = mBarColors.length - 2;
                high = mBarColors.length - 1;
            }
            return ColorUtils.getRGBGradient(mBarColors[low], mBarColors[high], (float) (1 - (((mBarColors.length - 1) * percent) % 1d)));
        } else if (mBarColors.length == 1) {
            return mBarColors[0];
        } else {
            return Color.BLACK.getValue();
        }
    }

    private void setTextSizeAndTextBoundsWithAutoTextSize(float unitGapWidthHalf, float unitWidth, float unitGapHeightHalf, float unitHeight, String text) {
        RectFloat textRect = mOuterTextBounds;

        if (mShowUnit) {

            //shrink text Rect so that there is space for the unit
            switch (mUnitPosition) {

                case TOP:
                    textRect = new RectFloat(mOuterTextBounds.left, mOuterTextBounds.top + unitHeight + unitGapHeightHalf, mOuterTextBounds.right, mOuterTextBounds.bottom);
                    break;
                case BOTTOM:
                    textRect = new RectFloat(mOuterTextBounds.left, mOuterTextBounds.top, mOuterTextBounds.right, mOuterTextBounds.bottom - unitHeight - unitGapHeightHalf);
                    break;
                case LEFT_TOP:
                case LEFT_BOTTOM:
                    textRect = new RectFloat(mOuterTextBounds.left + unitWidth + unitGapWidthHalf, mOuterTextBounds.top, mOuterTextBounds.right, mOuterTextBounds.bottom);
                    break;
                case RIGHT_TOP:
                case RIGHT_BOTTOM:
                default:
                    textRect = new RectFloat(mOuterTextBounds.left, mOuterTextBounds.top, mOuterTextBounds.right - unitWidth - unitGapWidthHalf, mOuterTextBounds.bottom);
                    break;
            }

        }

        mTextPaint.setTextSize((int) (calcTextSizeForRect(text, mTextPaint, textRect) * mTextScale));
        mActualTextBounds = calcTextBounds(text, mTextPaint, textRect); // center text in text rect
    }

    /**
     * Returns the bounding rectangle of the given _text, with the size and style defined in the _textPaint centered in the middle of the _textBounds
     *
     * @param _text       The text.
     * @param _textPaint  The paint defining the text size and style.
     * @param _textBounds The rect where the text will be centered.
     * @return The bounding box of the text centered in the _textBounds.
     */
    private RectFloat calcTextBounds(String _text, Paint _textPaint, RectFloat _textBounds) {

        Rect textBoundsTmp = new Rect();

        //get current text bounds
        textBoundsTmp = _textPaint.getTextBounds(_text);
        float width = textBoundsTmp.left + textBoundsTmp.getWidth();
        float height = textBoundsTmp.bottom + textBoundsTmp.getHeight() * 0.93f; // the height of calcTextBounds is a bit to high, therefore  * 0.93
        //center in circle
        RectFloat textRect = new RectFloat();
        textRect.left = (_textBounds.left + ((_textBounds.getWidth() - width) / 2));
        textRect.top = _textBounds.top + ((_textBounds.getHeight() - height) / 2);
        textRect.right = textRect.left + width;
        textRect.bottom = textRect.top + height;


        return textRect;
    }

    /**
     * Turn off spinning mode
     */
    public void stopSpinning() {
        setSpin(false);
        mAnimationHandler.sendEvent(AnimationMsg.STOP_SPINNING.ordinal());
    }

    /**
     * Puts the view in spin mode
     */
    public void spin() {
        setSpin(true);
        mAnimationHandler.sendEvent(AnimationMsg.START_SPINNING.ordinal());
    }

    private float getRotationAngleForPointFromStart(CvPoint point) {
        long angle = Math.round(calcRotationAngleInDegrees(mCenter, point));
        float fromStart = mDirection == Direction.CW ? angle - mStartAngle : mStartAngle - angle;
        return normalizeAngle(fromStart);
    }
}
